/*
Copyright (C) 2011 Salil Bhagurkar

This file is part of illusion

illusion is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

illusion is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <klib/lib.h>
#include <klib/format.h>
#include <klib/bitmap.h>
#include <klib/memory.h>
#include <klib/string.h>
#include <kernel/console.h>
#include <kernel/init.h>
#include <kernel/kmalloc.h>
#include <kernel/list.h>
#include <kernel/page.h>
#include <kernel/vmpage.h>
#include <kernel/process.h>
#include <kernel/vfs.h>
#include <kernel/sched.h>
#include <kernel/config.h>
#include <arch/power.h>
#include <tests/tests.h>
#include <kernel/errors.h>
#include <arch/portio.h>

/*
 * Each test depends on an init state which is a counter incremented
 * after each initialization in the init.c code. When an init state is reached,
 * the tests that require that init state to be reached, are executed.
 */

static uint_t total_count = 0;

static int __test_assert(char *desc, int result)
{
	if(result)
	{
		debug("Pass:test(%s)\n", desc);
		total_count++;
		return 1;
	}
	else
	{
		console_printf("FAIL:test(%s)\n", desc);
		return 0;
	}
}

#define test_assert(name, condition) if(!(__test_assert(name, condition))) return 0

static int bitmap_test()
{
	u32 map = 0x80000001;
	test_assert("Bitmap Test0", get_bit(&map, 0) == 1);
	test_assert("Bitmap Test1", get_bit(&map, 31) == 1);
	set_bit(&map, 16);
	test_assert("Bitmap Test2", get_bit(&map, 16) == 1);
	uint_t location;
	find_set_bit((void *)&map + 1, 32, &location);
	test_assert("Bitmap Test3", location == 8);
	find_clear_bit(&map, 32, &location);
	test_assert("Bitmap Test4", location == 1);
	clear_bit(&map, 16);
	test_assert("Bitmap Test5", get_bit(&map, 16) == 0);
	set_bit(&map, 16);
	test_assert("Bitmap Test6", count_set_bit(&map, 32) == 3);
	test_assert("Bitmap Test7", count_clear_bit(&map, 32) == 29);

	return 1;
}



/*
 * General library function testing
 */
static int lib_test()
{
	u8 buffer[1024];
	memset(buffer, 0, 30);
	memset(buffer + 10, 0xff, 10);
	test_assert("Library test1", buffer[10] == 0xff);
	test_assert("Library test2", buffer[19] == 0xff);
	test_assert("Library test3", buffer[9] == 0);
	test_assert("Library test4", buffer[20] == 0);


	format_string((char *)buffer, 1024, "1234%u1234", 1234);
	test_assert("Format string test1", buffer[4] == '1');
	test_assert("Format string test2", buffer[6] == '3');
	format_string((char *)buffer, 1024, "%shello", "bye");
	test_assert("Format string test3", buffer[0] == 'b');
	test_assert("Format string test4", buffer[1] == 'y');

	test_assert("Strlen test1", strlen("1234") == 4);
	test_assert("Strlen test2", strlen("") == 0);
	char *src = "Hello";
	char dst[6];
	strcpy(dst, src, 6);
	test_assert("Strcpy test1", strlen(dst) == 5);
	test_assert("Strcpy test2", dst[0] == 'H');
	test_assert("Strcpy test3", dst[4] == 'o');
	test_assert("Strcpy test4", dst[5] == '\0');
	test_assert("Streq test1", streq(dst, src) == 1);

	return 1;
}

static int phys_page_test()
{
	uint_t init_count = phys_count_free();
	void *pages[5];
	for(int i = 0; i < 5; i++) {
		pages[i] = phys_get_pages(2);
		phys_free_pages(pages[i] + PAGE_SIZE, 1);
	}
	test_assert("Phys page test1", phys_count_free() == (init_count - 5));
	for(int i = 0; i < 5; i++) {
		phys_free_pages(pages[i], 1);
	}
	test_assert("Phys page test2", phys_count_free() == init_count);

	return 1;
}

static int vm_page_test()
{
	uint_t init_count = vm_count_free(&kernel_vmmap);
	uint_t init_phys_count = phys_count_free();
	void *pages[5];
	for(int i = 0; i < 5; i++) {
		pages[i] = vm_get_pages(&kernel_vmmap, 2, PAGE_PRESENT | PAGE_WRITE);
		vm_free_pages(&kernel_vmmap, pages[i] + PAGE_SIZE, 1);
	}
	test_assert("VM page test1", vm_count_free(&kernel_vmmap) == (init_count - 5));
	test_assert("VM Phys page test1", phys_count_free() == (init_phys_count - 5));
	for(int i = 0; i < 5; i++) {
		vm_free_pages(&kernel_vmmap, pages[i], 1);
	}
	test_assert("VM page test2", vm_count_free(&kernel_vmmap) == init_count);
	test_assert("VM Phys page test2", phys_count_free() == init_phys_count);

	return 1;
}

static int test_dev0_sync()
{
	return 0;
}

static int vfs_test1()
{
	uint_t init_mem = memory_available();

	vfs_create_device("test_dev0", 1);
	vfs_create_device("test_dev1", 1);
	struct vfs_node *node;
	test_assert("VFS test1", vfs_get(&node, null, "test_dev0:") == 0);
	test_assert("VFS test2", vfs_get(&node, null, "test_dev1:") == 0);
	test_assert("VFS test3", vfs_get(&node, null, "test_dev2:") == ENODEV);

	vfs_get(&node, null, "test_dev0:");
	struct vfs_node *node0 = vfs_create(node, "test_node0");
	struct vfs_node *node1 = vfs_create(node0, "test_node1");
	struct vfs_node *_node1;
	vfs_get(&_node1, null, "test_dev0:/test_node0/test_node1");
	test_assert("VFS test4", node1 == _node1);

	vfs_remove_device("test_dev1", false);
	vfs_delete(node0);
	vfs_remove_device("test_dev0", false);

	test_assert("VFS test5", memory_available() == init_mem);


	return 1;
}

static int vfs_test2_sync_node_cb(struct vfs_node *node)
{
	return 0;
}

static int vfs_test2_sync_page_cb(struct vfs_node *node, struct vfs_page *page)
{
	return 0;
}

static int vfs_test2()
{
	char *create_chars(int c, uint_t length)
	{
		char *buf = (char *)kmalloc(length, "tests");
		memset(buf, c, length);
		return buf;
	}

	uint_t init_mem = memory_available();

	struct vfs_device *dev0 = vfs_create_device("dev0", 1);

	struct vfs_sync_provider dev0_prov = {
			.name = "dev0_prov",
			.sync_node = vfs_test2_sync_node_cb,
			.sync_page = vfs_test2_sync_page_cb,
			.get_child = null,
			.next_child = null,
	};

	vfs_set_sync_provider(dev0, &dev0_prov);

	struct vfs_node *dev0_root;
	vfs_get(&dev0_root, null, "dev0:");

	struct vfs_node *node = vfs_create(dev0_root, "node");

	char *bufa = create_chars('a', 10); //0
	char *bufb = create_chars('b', 10); //10
	char *bufc = create_chars('c', 10); //20
	char *bufd = create_chars('d', 10); //30
	char *bufe = create_chars('e', 10); //40
	char *buff = create_chars('f', 10); //50

	vfs_write(node, "data", bufa, 0, 10);
	vfs_write(node, "data", buff, 50, 10);
	vfs_write(node, "data", bufc, 20, 10);
	vfs_write(node, "data", bufb, 10, 10);

	vfs_read(node, "data", bufd, 30, 10);
	vfs_read(node, "data", bufd, 40, 10);

	vfs_delete(node);
	vfs_sync_dev_deletes(node);
	vfs_sync(node);

	kfree(bufa, 10, "tests");
	kfree(bufb, 10, "tests");
	kfree(bufc, 10, "tests");
	kfree(bufd, 10, "tests");
	kfree(bufe, 10, "tests");
	kfree(buff, 10, "tests");

	vfs_remove_device("dev0", false);

	test_assert("VFS2 test1", init_mem == memory_available());

	return 1;
}

static bool process_run_count = 0;
static void process_test_p()
{
	process_run_count++;
}

static int process_test()
{
	uint_t init_mem = memory_available();
	struct process *p1, *p2;
	create_kernel_process(&p1, "process_test1", process_test_p);
	create_kernel_process(&p2, "process_test2", process_test_p);
	sched_change_prio(p1, PRIO_HIGH);
	sched_run_process(p1);
	sched_change_prio(p2, PRIO_NORMAL); //Already normal
	sched_run_process(p2);
	void *evt_data;
	event_wait(&evt_data, p2->evt_exit); //This will run both processes
	process_kill(p1);
	process_kill(p2);
	test_assert("Process test1", process_run_count == 2);
	test_assert("Process test final", memory_available() == init_mem);
	return 1;
}

static struct event *e;
static void *event_data;
static void event_raiser()
{
	event_raise(e, event_data);
}

static int event_test()
{
	uint_t init_mem = memory_available();
	e = event_create("event_test");
	test_assert("Event test1", event_get("event_test") == null);
	event_register(e);
	test_assert("Event test2", event_get("event_test") == e);
	event_unregister(e);
	test_assert("Event test3", event_get("event_test") == null);
	event_data = (void *)0x1234;
	struct process *p;
	create_kernel_process(&p, "event_raiser", event_raiser);
	sched_run_process(p);
	void *event_data_ref;
	event_wait(&event_data_ref, e);
	process_kill(p);
	test_assert("Event test4", event_data == event_data_ref);
	event_free(e);
	test_assert("Event test5", memory_available() == init_mem);
	return 1;
}


static int stack_depth = 0;

static int serial_write(char *stream)
{
	while(*stream != '\0') {
		outb(0x3f8, (*stream));
		stream++;
	}
	return 0;
}

bool inst_enabled = false;

void __attribute__((no_instrument_function)) __cyg_profile_func_enter(void *func_address, void *call_site)
{
	if(inst_enabled)
	{
		stack_depth++;
	}
}

void __attribute__((no_instrument_function)) __cyg_profile_func_exit(void *func_address, void *call_site)
{
	if(inst_enabled)
	{
		stack_depth--;
	}
}

static int (*test_method[])() = {bitmap_test, lib_test, phys_page_test, vm_page_test, vfs_test1, vfs_test2, process_test, event_test};
static char test_method_init_states[] = {INIT_CONSOLE, INIT_CONSOLE, INIT_BITMAPS, INIT_MAP, INIT_VFS, INIT_VFS, INIT_PROCESS, INIT_PROCESS};

int perform_tests(uint_t init_state)
{
#ifdef CONFIG_RUN_TESTS
	uint_t nr_tests = sizeof(test_method) / sizeof(*test_method);

	for(uint_t i = 0; i < nr_tests; i++)
	{
		if(test_method_init_states[i] == init_state) {
			bool result = test_method[i]();

			assertv(result == true, "A test failed. Cannot proceed\n");
		}
	}

	if(init_state >= INIT_CONSOLE) {
		initmsg("State tests successful for init_state [%u]\n", init_state);
	}

	if(init_state == INIT_SYSTEM) {
		initmsg("Total tests succeeded : %u\n", total_count);
	}
#endif
	return 0;
}
